package whf.framework.tools.generator;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import whf.framework.dao.hibernate.HibernateIdGenerator;
import whf.framework.log.Log;
import whf.framework.log.LogFactory;
import whf.framework.meta.entity.Object;
import whf.framework.meta.entity.ObjectProperty;
import whf.framework.meta.service.ObjectService;
import whf.framework.meta.service.ObjectServiceImp;
import whf.framework.tools.generator.cfg.Config;
import whf.framework.tools.generator.cfg.ConfigFactory;
import whf.framework.tools.generator.db.Column;
import whf.framework.tools.generator.db.Table;
import whf.framework.util.BeanFactory;
import whf.framework.util.Output;
import whf.framework.util.StringUtils;
import whf.framework.util.Utils;

/**
 * @author wanghaifeng 代码生成工具
 * @todo 配置文件直接加到指定文件，
 * @todo jsp文件可以从配置模板中生成
 */
public class ClassGenerator {
	/**
	 * @property Log:log
	 */
	private static Log log = LogFactory.getLog(ClassGenerator.class);
	
	/**
	 * @property SimpleDateFormat:sdf
	 */
	private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

	/**
	 * @property String:boClassName
	 */
	private String boClassName;

	/**
	 * @property String:boClassName2
	 */
	private String boVariable;

	/**
	 * @property String:serviceClassName
	 */
	private String serviceClassName;

	/**
	 * @property String:serviceImpClassName
	 */
	private String serviceImpClassName;

	/**
	 * @property String:daoImpClassName
	 */
	private String daoImpClassName;

	/**
	 * @property String:daoClassName
	 */
	private String daoClassName;

	/**
	 * @property String:actionClassName
	 */
	private String actionClassName;

	/**
	 * @property Table:table
	 */
	private Table table;
	
	/**
	 * @property String:tableName
	 */
	private String tableName;
	
	/**
	 * @property String:now
	 */
	private String now = sdf.format(new Date());

	/**
	 * @property ObjectRelationMapping:orm
	 */
	private ObjectRelationMapping orm;

	//
	/**
	 * @property Output:springConfigOutput
	 */
	private static Output springConfigOutput;

	/**
	 * @property Output:strutsConfigOutput
	 */
	private static Output strutsConfigOutput;

	/**
	 * @property Output:hibernateConfigOutput
	 */
	private static Output hibernateConfigOutput;

	/**
	 * @property Output:metaConfigOutput
	 */
	private static Output metaConfigOutput;
	
	/**
	 * @property Output:webServiceOutput
	 */
	private static Output webserviceOutput;

	/**
	 * @property boolean:configInitialized
	 */
	private static boolean configInitialized;

	private Config config;
	/**
	 * @property Map<String,String>:reservedWords
	 */
	private Map<String, String> reservedWords = new HashMap<String, String>();
	//

	public ClassGenerator(Config config, ObjectRelationMapping orm) {
		this.config = config;
		this.orm = orm;
		this.boClassName = orm.getBoClassName();
		this.init();
	}

	public ClassGenerator(Config config, Table table) {
		this(config, table.toORM());
		this.table = table;
	}

	/**
	 * 初始化
	 * @modify wanghaifeng Aug 25, 2006 9:51:33 PM
	 */
	private void init() {
		this.boVariable = this.boClassName.substring(0, 1).toLowerCase()
				+ this.boClassName.substring(1);
		this.serviceClassName = this.boClassName + "Service";
		this.serviceImpClassName = this.boClassName + "ServiceImp";
		this.daoClassName = this.boClassName + "DAO";
		this.daoImpClassName = this.boClassName + "DAOImp";
		this.actionClassName = this.boClassName + "Action";
		
		if (this.table != null) {
			this.tableName = this.table.getTableName();
		} else {
			this.tableName = this.orm.getTableName();
		}
		this.reservedWords.put("{0}",this.boClassName);
		this.reservedWords.put("{1}",this.boVariable);
		this.reservedWords.put("{2}",this.config.getRootPackage());
		this.reservedWords.put("{3}",this.tableName);
		this.reservedWords.put("{4}",this.config.getSubModule());
		this.reservedWords.put("{5}",this.now);
		this.reservedWords.put("{6}","I"+this.boClassName);
		log.debug(this.reservedWords);
	}

	/**
	 * @property int:finishFileCount
	 */
	private int finishFileCount = 0;

	private void finish(String info) {
		finishFileCount++;
		log.debug("Finish outputting " + finishFileCount + ": " + info);
	}

	protected void outputDAO(String daoPath) throws IOException {
		FileOutputStream file = new FileOutputStream(daoPath + daoClassName + ".java");
		Output out = new Output(file);
		out.print(this.getTemplateConfig("java.dao"));
		out.close();
		this.finish(daoClassName + ".java");
	}

	protected void outputDAOImp(String daoPath) throws IOException {
		FileOutputStream file = new FileOutputStream(daoPath + daoImpClassName + ".java");
		Output out = new Output(file);
		out.print(this.getTemplateConfig("java.daoImp"));
		out.close();
		this.finish(daoImpClassName + ".java");
	}

	protected void outputService(String daoPath) throws IOException {
		FileOutputStream file = new FileOutputStream(daoPath + serviceClassName + ".java");
		Output out = new Output(file);
		out.print(this.getTemplateConfig("java.service"));
		out.close();
		this.finish(serviceClassName + ".java");
	}

	protected void outputServiceImp(String daoPath) throws IOException {
		FileOutputStream file = new FileOutputStream(daoPath + serviceImpClassName + ".java");
		Output out = new Output(file);
		out.print(this.getTemplateConfig("java.serviceImp"));
		out.close();
		this.finish(serviceImpClassName + ".java");
	}
	
	protected void outputWebservice(String webservicePath) throws IOException{
		FileOutputStream fos = new FileOutputStream(webservicePath + this.boClassName+"ServiceWS.java");
		Output out = new Output(fos);
		out.print(this.getTemplateConfig("java.webservice"));
		out.flush();
		out.close();
		fos = new FileOutputStream(webservicePath + this.boClassName+"ServiceWSImp.java");
		out = new Output(fos);
		out.print(this.getTemplateConfig("java.webserviceImp"));
		out.flush();
		out.close();
	}

	protected void outputStrutsAction(String actionPath) throws IOException {
		FileOutputStream file = new FileOutputStream(actionPath + actionClassName + ".java");
		Output out = new Output(file);
		out.print(this.getTemplateConfig("java.dispatch-action"));
		out.close();
		this.finish(actionClassName + ".java");
	}

	private String getTemplateConfig(String key) {
		String result = ConfigFactory.getInstance().getTemplateContent(key);
		result = StringUtils.replace2(this.reservedWords, result);
		return result;
	}

	private void outputConfigFiles() throws IOException {
		springConfigOutput.println("\t<!-- //////////     " + this.boClassName + "     //////////-->");
		metaConfigOutput.println("\t<!-- //////////     " + this.boClassName + "     //////////-->");
		hibernateConfigOutput.println("\t<!-- //////////     " + this.boClassName + "     //////////-->");
		strutsConfigOutput.println("\t<!-- //////////     " + this.boClassName + "     //////////-->");
		webserviceOutput.println("\t<!-- //////////     " + this.boClassName + "     //////////-->");

		String hibernateConfigInfo = "<mapping resource=\""
				+ StringUtils.replace(this.config.getRootPackage(), ".", "/") + "/entity/"
				+ boClassName + ".hbm.xml" + "\"/>";
		hibernateConfigOutput.println("\t" + hibernateConfigInfo);
		//
		// String shortSpringStyle =
		// this.getConfigString("Configuration.Short-Style");

		String longSpringStyle = this.getTemplateConfig("xml.spring");
		springConfigOutput.println("\t" + longSpringStyle);
		//
		String strutsConfig = this.getTemplateConfig("xml.struts");
		strutsConfigOutput.println("\t" + strutsConfig);
		//
		String metaConfig = this.getTemplateConfig("xml.meta");
		metaConfigOutput.println("\t" + metaConfig);
		//
		String webServiceConfig = this.getTemplateConfig("xml.webservice");
		webserviceOutput.println("\t"+webServiceConfig);
		//
		this.finish(boClassName + ".cfg.txt");
	}

	protected void outputListJSP(String jspPath, String htmlHeader,
			String htmlFooter) throws IOException {
		String listjsp = jspPath + "list.jsp";
		FileOutputStream listFile = new FileOutputStream(listjsp);
		Output listOut = new Output(listFile);
		listOut.println(htmlHeader);

		String entity = this.config.getRootPackage() + ".entity." + this.boClassName;
		listOut.println("<framework:table pkStyle=\"\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\" boClassName=\""
						+ entity + "\" navPosition=\"both\">");
		listOut.println("</framework:table>");

		listOut.println(htmlFooter);
		listFile.close();
		this.finish(listjsp);
	}

	protected void outputViewJSP(String jspPath, String htmlHeader,
			String htmlFooter) throws IOException {
		String viewjsp = jspPath + "view.jsp";
		FileOutputStream viewFile = new FileOutputStream(viewjsp);
		Output viewOut = new Output(viewFile);
		viewOut.println(htmlHeader);
		viewOut.println("<table>");
		if (table == null) {
			for (int i = 0; i < this.orm.getProperties().length; i++) {
				viewOut.println("<tr");
				if (i % 2 == 0) {
					viewOut.print("class=\"dataEvenRow\">");
				} else {
					viewOut.print("class=\"dataOddRow\">");
				}
				viewOut.println("\t<td class=\"dataLabel\">").print(
						this.orm.getProperties()).print("</td>");
				viewOut.println("\t<td class=\"dataField\">").print(
						"<bean:write name=\"" + boClassName
								+ "Form\" property=\""
								+ this.orm.getProperties()[i] + "\"/>").print(
						"</td>");
				viewOut.println("</tr>");
			}
		} else {
			for (int i = 0; i < table.getColumns().size(); i++) {
				Column col = (Column) table.getColumns().get(i);
				viewOut.println("<tr");
				if (i % 2 == 0) {
					viewOut.print("class=\"dataEvenRow\">");
				} else {
					viewOut.print("class=\"dataOddRow\">");
				}
				viewOut.println("\t<td class=\"dataLabel\">").print(
						col.getPropertyName()).print("</td>");
				viewOut.println("\t<td class=\"dataField\">").print(
						"<bean:write name=\"" + boClassName
								+ "Form\" property=\"" + col.getPropertyName()
								+ "\"/>").print("</td>");
				viewOut.println("</tr>");
			}
		}
		viewOut.println("</table>");
		viewOut.println("<html:submit>").print(
				"<bean:message key=\"form.submit\" />").print("</html:submit");
		viewOut.println("<html:reset>").print(
				"<bean:message key=\"form.reset\" />").print("</html:reset");
		viewOut.println(htmlFooter);
		viewFile.close();
		this.finish(viewjsp);
	}

	protected void outputEditJSP(String jspPath, String htmlHeader,
			String htmlFooter) throws IOException {
		String editjsp = jspPath + "edit.jsp";
		FileOutputStream editFile = new FileOutputStream(editjsp);
		Output editOut = new Output(editFile);
		editOut.println(htmlHeader);
		editOut.println("<table>");
		if (table == null) {
			for (String p : this.orm.getProperties()) {
				editOut.println("<tr>");
				editOut.println("\t<td class=\"dataLabel\">").print(p).print(
						"</td>");
				editOut.println("\t<td  class=\"dataField\">").print(
						"<html:text property=\"" + p + "\"/>").print("</td>");
				editOut.println("</tr>");
			}
		} else {
			for (Column col : table.getColumns()) {
				editOut.println("<tr>");
				editOut.println("\t<td class=\"dataLabel\">").print(
						col.getPropertyName()).print("</td>");
				editOut.println("\t<td  class=\"dataField\">").print(
						"<html:text property=\"" + col.getPropertyName()
								+ "\"/>").print("</td>");
				editOut.println("</tr>");
			}
		}
		editOut.println("</table>");
		editOut.println(htmlFooter);
		editFile.close();
		this.finish(editjsp);
	}

	protected void outputJsp(String jspPath, String type) throws IOException {
		String jsp = jspPath + type + ".jsp";
		FileOutputStream fos = new FileOutputStream(jsp);
		Output out = new Output(fos);
		String text = this.getTemplateConfig("jsp." + type);
		out.print(text);
		fos.close();
		this.finish(jsp);
	}

	private void outputModel(String boPath) throws IOException {
		EntityGenerator boGen = null;
		String boPackage = this.config.getRootPackage();
		if (table == null) {
			boGen = new EntityGenerator(boPackage, boClassName, this.orm.getTypes(), this.orm.getProperties());
		} else {
			boGen = new EntityGenerator(boPackage, table);
		}
		FileOutputStream boFile = new FileOutputStream(boPath + boClassName + ".java");
		Output out = new Output(boFile);
		boGen.output(out);
		boFile.close();
		this.finish(boClassName + ".java");
		//
//		boFile = new FileOutputStream(boPath + "I" + boClassName + ".java");
//		out = new Output(boFile);
//		out.print("package ").print(this.config.getRootPackage() + ".entity;");
//		out.println("\nimport ").print(Entity.class.getName()+";");
//		out.println("\npublic interface I"+boClassName+" extends Entity{");
//		List<Column> cols = this.table.getColumns();
//		for(Column col: cols){
//			out.println("\tpublic " + col.getPropertyType() + " get"+StringUtils.capitalize(col.getPropertyName())+"();\n");
//			out.println("\tpublic void set"+StringUtils.capitalize(col.getPropertyName())+"(" + col.getPropertyType() + " "+col.getPropertyName()+" );\n");
//		}
//		out.println("}");
//		out.flush();
//		out.close();
	}

	private void outputHbm(String boPath) throws IOException {
		FileOutputStream hbmFile = new FileOutputStream(boPath + boClassName
				+ ".hbm.xml");
		Output hbmOut = new Output(hbmFile);
		hbmOut.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
		hbmOut.println("<!DOCTYPE hibernate-mapping ");
		hbmOut.println("\tPUBLIC \"-//Hibernate/Hibernate Mapping DTD 3.0//EN\" ");
		hbmOut.println("\t\"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd\">");
		hbmOut.println("<hibernate-mapping>");
		String tableName = "";
		String pkColumn = "id";
		if (table != null) {
			tableName = table.getTableName();
			Column col = table.findPrimaryKeyColumn();
			if (col != null) {
				pkColumn = col.getColumnName();
			} else {
				pkColumn = table.getTableName() + "_id";
			}
		} else {
			tableName = orm.getTableName();
			if (StringUtils.isEmpty(tableName)) {
				tableName = orm.getBoClassName();
			}
			pkColumn = tableName + "_id";
		}

		hbmOut.println("\t<class name=\"" + this.config.getRootPackage() + ".entity." + boClassName
				+ "\" table=\"" + tableName + "\" lazy=\"false\">");
		hbmOut.println("\t\t<id name=\"id\" column=\"" + pkColumn + "\" >");
		hbmOut.println("\t\t\t<generator class=\""
				+ HibernateIdGenerator.class.getName() + "\" >");
		hbmOut.println("\t\t\t\t<param name=\"category\">"
				+ this.boClassName.toUpperCase() + "</param>");
		hbmOut.println("\t\t\t</generator>");
		hbmOut.println("\t\t</id>");
		if (table == null) {
			for (int i = 0; this.orm.getProperties() != null
					&& i < this.orm.getProperties().length; i++) {
				if ("id".equals(this.orm.getProperties()[i]))
					continue;
				String p = this.orm.getProperties()[i];
				if (StringUtils.equalsIgnoreCase(this.orm.getTypes()[i],
						"boolean")
						&& p.toLowerCase().startsWith("is")) {
					p = p.substring(2);
				}
				hbmOut.println("\t\t<property name=\"" + p + "\" column=\""
						+ this.orm.getFields()[i] + "\" />");
			}
		} else {
			for (int i = 0; i < table.getColumns().size(); i++) {
				Column col = (Column) table.getColumns().get(i);
				if (col.isPrimaryKey() && !col.isForeign()) {
					continue;
				}
				String unique = String.valueOf(col.isUnique());
				String pn = col.getPropertyName();
				if (col.isForeign()) {
					if (pn.toLowerCase().endsWith("id")) {
						pn = pn.substring(0, pn.length() - 2);
					}
					hbmOut.println("\t\t<many-to-one name=\"" + pn
							+ "\" column=\"" + col.getColumnName()
							+ "\" unique=\"" + unique + "\"/>");
				} else {
					hbmOut.println("\t\t<property name=\"" + pn
							+ "\" column=\"" + col.getColumnName()
							+ "\" unique=\"" + unique + "\" />");
				}
			}
		}
		hbmOut.println("\t</class>");
		hbmOut.println("</hibernate-mapping>");
		hbmOut.flush();
		hbmOut.close();
		this.finish(boClassName + ".hbm.xml");
	}

	/**
	 * 保存数据库信息
	 * @modify wanghaifeng Aug 25, 2006 9:57:20 PM
	 * @throws GeneratorException
	 */
	public void saveOrUpdateMetaInfo() throws GeneratorException {
		BeanFactory.getSessionFactory();
		String boClass = this.config.getRootPackage() + ".entity." + boClassName;
		try {
			boolean create = false;
			ObjectService service = ObjectServiceImp.getObjectService();
			Object obj = service.findByEntityClass(boClass);
			if (obj == null) {
				obj = new Object();
				obj.setName(table.getTableComment());
				obj.setBoClassName(boClass);
				create = true;
			}
			obj.setActionClassName(this.config.getRootPackage() + ".web." + this.actionClassName);
			obj.setActive(true);
			obj.setDaoClassName(this.config.getRootPackage() + ".dao." + this.daoImpClassName);
			obj.setDaoName(this.config.getSubModule() + "." + this.daoClassName);
			obj.setSubModule(this.config.getSubModule());
			obj.setObjectType(this.config.getSubModule());
			if (this.table != null) {
				obj.setPersistenceTable(this.table.getTableName());
			} else {
				obj.setPersistenceTable(this.orm.getTableName());
			}
			obj.setServiceClassName(this.config.getRootPackage() + ".service." + this.serviceImpClassName);
			obj.setServiceName(this.config.getSubModule() + "." + this.serviceClassName);
			obj.setCode(this.boClassName);
			obj.setPath("/"+this.config.getSubModule()+"/"+this.boClassName);
			Set<ObjectProperty> props = new HashSet<ObjectProperty>();
			if (this.table != null) {
				int sortOrder = 10;
				for (Column col : this.table.getColumns()) {
					String propertyName = col.getPropertyName();
					ObjectProperty p = obj.findProperty(propertyName);
					if(p == null){
						p = new ObjectProperty();
						((ObjectProperty)p).setBoObject(obj);
						((ObjectProperty)p).setActive(true);
					}
					p.setVisible(true);
					p.setSortOrder(sortOrder);
					p.setName(col.getPropertyName());
					p.setDataType(col.getPropertyType());
					p.setDbColumnName(col.getColumnName());
					p.setPrimaryKey(col.isPrimaryKey());
					p.setMaxLength(col.getCharacterMaxLength());
					p.setDisplayTitle(col.getColumnComment());
					p.setRequired(!col.isNullable());
					props.add(p);
					
					sortOrder += 10;
				}
			}
			obj.setProperties(props);
			if (create) {
				service.create(obj);
			} else {
				service.update(obj);
			}
		} catch (Exception e) {
			log.error(this, e);
		}
	}

	public void generate() throws GeneratorException, IOException {
		String[] packagePaths = StringUtils.split(this.config.getRootPackage(), ".");
		String javaRoot = this.config.getJavaDir();
		String jspRoot = this.config.getJspDir();
		String subModule = this.config.getSubModule();
		
		if (!javaRoot.endsWith(File.separator)) {
			javaRoot += File.separator;
		}
		if (!jspRoot.endsWith(File.separator)) {
			jspRoot += File.separator;
		}
		for (int i = 0; packagePaths != null && i < packagePaths.length; i++) {
			javaRoot += packagePaths[i] + File.separator;
		}
		// mk dirs
		String boPath = javaRoot + "entity" + File.separator;
		String daoPath = javaRoot + "dao" + File.separator;
		String servicePath = javaRoot + "service" + File.separator;
		String webPath = javaRoot + "web" + File.separator;
		String webservicePath = javaRoot +"webservice"+File.separator;

		if (this.config.isEnableConfig())
			Utils.mkdir(javaRoot);
		if (this.config.isEnableModel() || this.config.isEnableHbm())
			Utils.mkdir(boPath);
		if (this.config.isEnableDao() || this.config.isEnableDaoImp())
			Utils.mkdir(daoPath);
		if (this.config.isEnableService() || this.config.isEnableServiceImp())
			Utils.mkdir(servicePath);
		if (this.config.isEnableAction())
			Utils.mkdir(webPath);
		if (this.config.isEnableWebservice())
			Utils.mkdir(webservicePath);

		if (!configInitialized && this.config.isEnableConfig()) {
			String configFile = javaRoot + "spring.xml";
			FileOutputStream fos = new FileOutputStream(configFile);
			springConfigOutput = new Output(fos);
			springConfigOutput.println("<beans>");

			fos = new FileOutputStream(javaRoot + "meta.xml");
			metaConfigOutput = new Output(fos);
			metaConfigOutput.println("<metas>");

			fos = new FileOutputStream(javaRoot + "hibernate.xml");
			hibernateConfigOutput = new Output(fos);
			hibernateConfigOutput.println("<configurations>");

			fos = new FileOutputStream(javaRoot + "struts.xml");
			strutsConfigOutput = new Output(fos);
			strutsConfigOutput.println("<struts>");
			
			fos = new FileOutputStream(javaRoot + "webservice.xml");
			webserviceOutput = new Output(fos);
			webserviceOutput.println("<beans>");
			//
			
			configInitialized = true;
		}
		// create bo
		if (this.config.isEnableModel())
			this.outputModel(boPath);
		if (this.config.isEnableHbm())
			this.outputHbm(boPath);
		if (this.config.isEnableDao())
			this.outputDAO(daoPath);
		if (this.config.isEnableDao())
			this.outputDAOImp(daoPath);
		if (this.config.isEnableService())
			this.outputService(servicePath);
		if (this.config.isEnableServiceImp())
			this.outputServiceImp(servicePath);
		if (this.config.isEnableAction())
			this.outputStrutsAction(webPath);
		if (this.config.isEnableConfig())
			this.outputConfigFiles();
		if (this.config.isEnableMeta())
			this.saveOrUpdateMetaInfo();
		if (this.config.isEnableWebservice())
			this.outputWebservice(webservicePath);
		//
		if (config.isEnableJsp()) {
			String jspPath = jspRoot + subModule + File.separator
					+ this.boClassName + File.separator;
			Utils.mkdir(jspPath);
			this.outputJsp(jspPath, "list");
			this.outputJsp(jspPath, "view");
			this.outputJsp(jspPath, "edit");
		}
	}

	/**
	 * @modify wanghaifeng Aug 25, 2006 9:48:59 PM
	 */
	public static void doFinalize() {
		try {
			configInitialized = false;
			if (springConfigOutput != null){
				springConfigOutput.println("</beans>");
				springConfigOutput.flush();
				springConfigOutput.close();
				springConfigOutput = null;
			}
			if (metaConfigOutput != null){
				metaConfigOutput.println("</metas>");
				metaConfigOutput.flush();
				metaConfigOutput.close();
				metaConfigOutput = null;
			}
			if (hibernateConfigOutput != null){
				hibernateConfigOutput.println("</configurations>");
				hibernateConfigOutput.flush();
				hibernateConfigOutput.close();
				hibernateConfigOutput = null;
			}
			if (strutsConfigOutput != null){
				strutsConfigOutput.println("</struts>");
				strutsConfigOutput.flush();
				strutsConfigOutput.close();
				strutsConfigOutput = null;
			}
			if(webserviceOutput != null){
				webserviceOutput.println("</beans>");
				webserviceOutput.flush();
				webserviceOutput.close();
				webserviceOutput = null;
			}
		} catch (Exception e) {
			log.error(ClassGenerator.class, e);
		}
	}

	/**
	 * @modify wanghaifeng Aug 25, 2006 9:48:59 PM
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {
	}

}
